grammar Calc;

options {
    backtrack=true;
}

@header {
package org.antlr.calc;

import java.util.HashMap;
import java.lang.Math;
import java.math.MathContext;
import java.math.BigDecimal;
import java.util.Scanner;
import java.io.*;
import org.antlr.calc.*;
import org.antlr.calc.exceptions.*;
}


@lexer::header {
package org.antlr.calc;
import org.antlr.calc.exceptions.*;
}

@lexer::members {
@Override
public void emitErrorMessage(String msg) {
    //System.err.println(msg);
    throw new RecognitionCalculationException(msg);
}
}

@members {
public static void main(String[] args) throws Exception {
    HashMap<String,Object> memory = new HashMap<String,Object>();
	System.out.println("Type 'exit' to quit");
	String line = "";
	Scanner scanner = new Scanner(System.in);

    while(!((line = scanner.nextLine()).equals("exit"))){
        System.out.println("input: " + line);
		CalcLexer lexer = new CalcLexer(new ANTLRStringStream(line));
		CommonTokenStream tokens = new CommonTokenStream(lexer);
		CalcParser parser = new CalcParser(tokens);
        parser.setMemory(memory);
		System.out.println(parser.prog());
	}
        
}

private HashMap<String,Object> memory = new HashMap<String,Object>();

public void setMemory(HashMap<String,Object> memory) {
    this.memory = memory;
}

@Override
public void emitErrorMessage(String msg) {
    //System.err.println(msg);
    throw new ParseCalculationException(msg);
}

}

// auto generated code !

prog returns [BigDecimal value]
    :   (stat {
        if ($value == null) {
            $value = $stat.value;
        } else {
            throw new CalculationException("More then one statement were found");
        }
    })+ ;

stat returns [BigDecimal value]
    :   exp {$value = $exp.value;}
    |   letExp {$value = $letExp.value;}
/*    |   printExp {$value = new BigDecimal();}*/
    ;

letExp returns [BigDecimal value]
    : VAR '=' exp
    {
        $value = $exp.value;
        memory.put($VAR.text, $exp.value);
        System.out.println($VAR.text + " is set to " + $exp.value);
    }
;

exp returns [BigDecimal value]
    :   a=multExp {$value = $a.value;}
        (   '+' b=multExp {$value = $value.add($b.value);}
        |   '-' b=multExp {$value = $value.subtract($b.value);}
        )*
    ;

multExp returns [BigDecimal value]
    :   a=powExpr {$value = $a.value;}
	(    '*' b=powExpr {$value = $value.multiply($b.value);}
	|    '/' b=powExpr {MathContext mc = new MathContext(15);
		 double dbl1 = Double.parseDouble($value.toPlainString());
		 double dbl2 = Double.parseDouble($b.value.toPlainString());
		 dbl1 = dbl1/dbl2;
		 $value = new BigDecimal(dbl1,mc);}
	)*
    ;

powExpr returns [BigDecimal value]
    :   a=atom {$value = $a.value;}
        (
            POW  b=atom { $value = new BigDecimal(Math.pow($value.doubleValue(), $b.value.doubleValue())); }
        |   SQR  b=atom { $value = new BigDecimal(Math.pow($value.doubleValue(), 1 / $b.value.doubleValue())); }
        )*
    ;

atom returns [BigDecimal value]
    :   BD {
            try {$value = new BigDecimal($BD.text);}
            catch (Exception ex) {throw new ParseCalculationException("Can't process decimal " + $BD.text, ex);}
        }
    |   VAR 
        {
            BigDecimal v = (BigDecimal)memory.get($VAR.text);
            if ( v!=null ) { $value = v; }
            else {throw new ParseCalculationException("Unsupported variable");}
        }
    |   TRIG '(' exp ')' {
	MathContext mc = new MathContext(15);
	if($TRIG.text.toLowerCase().equals("sin") ) {
		double dbl = Math.sin(Double.parseDouble($exp.value.toPlainString()));
		$value = new BigDecimal(dbl,mc);}
	if($TRIG.text.toLowerCase().equals("cos") ) {
		double dbl = Math.cos(Double.parseDouble($exp.value.toPlainString()));
		$value = new BigDecimal(dbl,mc);}
	if($TRIG.text.toLowerCase().equals("tan") ) {
		double dbl = Math.tan(Double.parseDouble($exp.value.toPlainString()));
		$value = new BigDecimal(dbl,mc);}
	}
    |   '(' exp ')' {$value = $exp.value;}
    ;

/*printExp : PRINT
	(
        exp {System.out.println($exp.text);}
	)    
    ;*/

/*Lexer rules*/

PRINT : 'print'|'PRINT'|'Print' ;
TRIG :  'sin'|'cos'|'tan'|'SIN'|'COS'|'TAN';
SQR : 'sqr' | 'SQR';
POW : '^';
VAR :   ('a'..'z'|'A'..'Z')('a'..'z'|'A'..'Z'|'_'|'0'..'9')* ;
BD :   ('0' | ('1'..'9') ('0'..'9')*)? ('.' ('0'..'9')+)? ;

WS  :   ( ' '
        | '\t'
        | '\r'
        | '\n'
        ) {$channel=HIDDEN;}
    ;